<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script>
        /**
         * watch的监听器
         * **/

        /* 
            Vue.prototype.$watch = function (expOrFn, cb, options) {
                const vm = this
                options = options || {}
                // 执行new Watcher获取$watch的基本功能
                const watcher = new Watcher(vm, expOrFn, options)
                // 如果有immediate属性，则立即执行一次cb
                if (options.immediate) {
                    cb.call(vm, watcher.value)
                }
                // 返回unwatchFn函数，用于取消观察数据
                return function unwatchFn () {
                  // 把'watcher实例'从当前正在观察的'状态的依赖列表'中移除
                  watcher.teardown()
                }
            }
        */

        class Watcher {
            constructor (vm, expOrfn, cb) {
                this.vm = vm
                this.deps = [] // 新增
                this.depsId = new Set() // 新增 => 如果当前Watcher已经订阅了该Dep，则不会重复订阅
                // 修改：如果expOrfn是函数，则直接赋值为getter
                // 在这种情形下，expOrfn函数中读取的所有变量都会被Watcher观察，如果该函数从Vue.js实例中读取了两个变量，则Watcher会同时观察这两个函数
                // computed实现原理也和这个函数（expOrfn函数）有关系
                if (typeof expOrfn === 'function') {
                    this.getter = expOrfn
                } else {
                    // 不是函数，是keypath 则是a.b.c.d
                    // Watcher函数会读取观察这个数据
                    this.getter = parsePath(expOrfn)
                }
                this.cb = cb
                this.value = this.get()
            }
            
            get () {
                /* *
                    构造器的get执行 => 当前的watcher实例给window.target => 调用getter方法，收集依赖 push到dep数组 收集了依赖
                * */
                window.target = this
                // 该函数访问对象深层次属性
                let value = this.getter.call(this.vm, this.vm)
                window.target = undefined
                return value
            }

            /* **
                data.a.b.c的值发生变化 => 调用setter 触发notify => 所有依赖项循环调用update => 触发get方法 => 执行cb回调函数，就是(newVal, oldVal) => {} 
            */
            update () {
                const oldValue = this.value
                // 拿到getter里面最新的值,不能再次调用get，否则会把当前watcher再次加入到subs数组中，导致递归死循环
                this.value = this.getter.call(this.vm, this.vm)
                this.cb.call(this.vm, this.value, oldValue)
            }

        }

        const bailRE = /^\w.$/


        /* 
            parsePath的作用是处理对象的深层次属性访问，比如data: {a: {b: {c:}}}要访问data.a.b.c，当把这个传递给watch函数时
            先访问obj[a]，然后obj = obj[a] 再访问obj[a][b] 再是obj[a][b][c]
        */
        function parsePath (path) {
            if (bailRE.test(path)) {
                return
            }
            let segments = path.split('.')
            return function (obj) {
                for (let i = 0; i < segments.length; i++) {
                    if (!obj) return
                    obj = obj[segments[i]]
                }
                return obj
            } 
        }


        /* *
            需要为对象的所有属性都添加响应式，而不是单个属性添加响应式
        * */


        // def工具函数
        function def (obj, key, val, enumerable) {
            debugger
            // 添加__ob__属性用
            Object.defineProperty(obj, key, {
                value: val,
                enumerable: !!enumerable,
                configurable: true,
                writable: true
            })
        }


        const arrayProto = Array.prototype
        const arrayMethods = Object.create(arrayProto);
        ['push',
        'pop',
        'unshift',
        'shift',
        'splice',
        'sort',
        'reverse'].forEach(method =>{
            const original = arrayProto[method]
            def(arrayMethods, method, function mutator (...args) {
                const result = original.apply(this, args)
                const ob = this.__ob__
                let inserted
                switch (method) {
                    case 'push':
                    case 'unshift': 
                        inserted = args
                        break
                    case 'splice': 
                        inserted = args.slice(2)
                        break
                }
                if (inserted) {
                    // 把新元素转化为响应式
                    ob.observeArray(inserted)
                }
                ob.dep.notify()
                return result
            })
        })

        // 是否有__proto__属性
        const hasProto = '__proto__' in {}
        const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
        class Observer {
            constructor (value) {
                debugger
                this.value = value
                this.dep = new Dep()
                // 将__ob__属性存到Observer实例上
                def(value, '__ob__', this)
                // 如果是数组
                if (Array.isArray(value)) {
                    // 覆盖数组的原型
                    // value.__proto__ = arrayMethods
                    // 这样也可以实现
                    // Object.setPrototypeOf(value, arrayMethods)

                    // 修改为
                    debugger
                    const augment = hasProto ? protoAugment : copyAugment
                    augment(value, arrayMethods, arrayKeys)

                    this.observeArray(value)
                }
                // 如果是对象
                else {
                    this.walk(value)
                }
            }

            walk (obj) {
                const keys = Object.keys(obj) 
                for (let i = 0; i < keys.length; i++) {
                    defineReactive(obj, keys[i], obj[keys[i]])
                }
            }

            observeArray (items) {
                // 数组所有子数据转化为响应式
                debugger
                for (let i = 0, l = items.length; i < l; i++) {
                    debugger
                    observe(items[i])
                }
            }
        }

        function protoAugment (target, src, keys) {
            debugger
            target.__proto__ = src
        }
        // 如果不支持__proto__，直接将arrayMethods上面的方法挂载到到被侦测的数组上
        function copyAugment (target, src, keys) {
            for (let i = 0, l = keys.length; i < keys.length, i < l; i++) {
                const key = keys[i]
                def(target, key, src[key])
            }
        }
        class Dep {
            constructor () {
                this.subs = []
            }
            addSub (sub) {
                this.subs.push(sub)
            }
            removeSub (sub) {
                this.remove(this.subs, sub)
            }
            depend (sub) {
                // 啥用？
                if (window.target) {
                    this.addSub(window.target)
                }
            }
            notify () {
                // 通知依赖执行
                for (let i = 0; i < this.subs.length; i++) {
                    console.log(this.subs, 'this.subs');
                    this.subs[i].update()
                }
            }
        }

        // 移除函数
        function remove (array, item) {
            if (array.length) {
                let index = array.indexOf(item)
                if (index > -1) {
                    array.splice(index, 1)
                }
            }
        }


        // 判断是否是对象
        function isObject (value) {
            return typeof value === 'object'
        }
        // 判断属性是否是对象自身的
        function hasOwn (value, property) {
            Object.hasOwn(value, property)
        }
        
        /* 
        *
        *尝试为value创建一个Obserser实例，
        *如果创建成功，直接返回新创建的Obserser实例
        *如果value已经存在一个Observer实例，则直接返回它
        */
        function observe (value) {
            debugger
            // 不是对象
            if (!isObject(value)) return
            let ob;
            if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
                // 已经存在响应式属性ob => 数组已经是响应式数据了
                ob = value.__ob__
            } else {
                // 数组此时还不是响应式数据
                ob = new Observer(value)
            }
            return ob
        }
        // 改造该函数
        function defineReactive (data, key, val) {
            if (typeof val === 'object') {
                new Observer(val)
            }
            let childOb = observe(val) // 数组的Observer实例
            let dep = new Dep()
            Object.defineProperty(data, key, {
                enumerable: true,
                configurable: true,
                get: function () {
                    // 为对象收集依赖
                    dep.depend()
                    // 为数组收集依赖
                    if (childOb) {
                        childOb.dep.depend()
                    }
                    return val
                },
                set: function (newVal) {
                    if (val === newVal) {
                        return
                    }
                    val = newVal
                    // 通知依赖
                    dep.notify()
                    console.log(val, 'val');
                }
            })
        }

        // let xhg = {
        //     height: 180
        // }
        // let data = {
        //     a: {
        //         b: {
        //             c: '1'
        //         }
        //     }
        // }
        // new Observer(data)
        // const watcher = new Watcher(data, 'a.b.c', (newVal, oldVal) => {
        //     console.log(newVal, 'newVal');
        // })
        // data.a.b.c = '2'
        // console.log(data, 'data');

        let list = [1, {a: '3'}, ['1', '2']]
        new Observer(list)
        console.log(list, 'list');
        list[2].push(['aaaaaaa'])
        
        let list2 = [2, {b: '3'}, ['1', '2']]
        console.log(list2, 'list2');
    </script>
</body>
</html>